/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.sensor.cropcircle;

import nl.cloudfarming.sensor.FileReadException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import nl.cloudfarming.sensor.AlreadyParsedException;
import nl.cloudfarming.sensor.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EnumMap;
import nl.cloudfarming.sensor.ParsedUnit;
import nl.cloudfarming.sensor.Parser;
import nl.cloudfarming.sensor.PayloadMetadata;
import nl.cloudfarming.sensor.model.GeoSensorDataBO;
import nl.cloudfarming.sensor.model.GeoSensorDataFileBO;
import nl.cloudfarming.sensor.model.GeoSensorDataFileDAO;
import nl.cloudfarming.sensor.util.MD5Sum;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class CropCircleParser implements Parser {

    private static Log log = LogFactory.getLog(CropCircleParser.class);
    private EnumMap map = new EnumMap<CropCircleFileDescription, Object>(CropCircleFileDescription.class);
    private BufferedReader input;
    
    @Autowired
    private GeoSensorDataFileDAO dao;

    public void setDao(GeoSensorDataFileDAO dao) {
        this.dao = dao;
    }
     
    @Override
     public ParsedUnit parse(Object payload) throws ParseException, NoSuchAlgorithmException, IOException, FileReadException{
        if(((File)payload).canRead()){
            String checkSum = MD5Sum.getHex(MD5Sum.createChecksum(new FileInputStream((File)payload)));
            input = new BufferedReader(new FileReader((File)payload));
            String line;
            PayloadMetadata payloadMetadata =  new PayloadMetadata(((File)payload).getName(),checkSum,((File)payload).lastModified());
            while ((line = input.readLine()) != null) {
                return parse(line, payloadMetadata);
            }
        } else{
            throw new FileReadException();
        }
        return null;

    }

    
    public ParsedUnit parse(String payload, PayloadMetadata payloadMetadata)throws ParseException, NoSuchAlgorithmException, IOException{
        // First create a entity representation of a file
        GeoSensorDataFileBO geoSensorDataFileBO = dao.newInstance(payloadMetadata.getFileName(), payloadMetadata.getCheckSum(), new Date(payloadMetadata.getFileCreated()), new Date());

        //converting the differrent data
        for (CropCircleFileDescription c : CropCircleFileDescription.values()) {
            if (c.getEndPossition() <= payload.length()) {
                if (c.getTheClass().equals(java.lang.Double.class)) {
                    double theDouble = parseDouble(payload.substring((c.getStartPosition() - 1), c.getEndPossition()), c.name());
                    map.put(c, theDouble);
                } else if (c.getTheClass().equals(java.lang.Integer.class)) {
                    int theInt = parseInt(payload.substring((c.getStartPosition() - 1), c.getEndPossition()), c.name());
                    map.put(c, theInt);
                } else if (c.getTheClass().equals(java.util.Date.class)) {
                    Date theDate = parseTime(payload.substring((c.getStartPosition() - 1), c.getEndPossition()), c.name());
                    map.put(c, theDate);
                } else {
                    map.put(c, payload.substring((c.getStartPosition() - 1), c.getEndPossition()));
                }
            } else if (!c.isMandatory()) {
                if (c.getTheClass().equals(java.lang.Double.class)) {
                    map.put(c, 0);
                } else if (c.getTheClass().equals(java.lang.Integer.class)) {
                    map.put(c, 0);
                } else if (c.getTheClass().equals(java.util.Date.class)) {
                    map.put(c, null);
                } else {
                    map.put(c, "");
                }
            } else {
                log.error("Unknown data conversion Exception while parsing fieldname: " + c.name() + " Value: " + payload.substring(c.getStartPosition(), c.getEndPossition()));
            }
        }
        return new GeoSensorDataBO(geoSensorDataFileBO, (Double) map.get(CropCircleFileDescription.LONGITUDE), (Double) map.get(CropCircleFileDescription.LATITUDE), (Integer) map.get(CropCircleFileDescription.FIX_TYPE), (Date) map.get(CropCircleFileDescription.UTC_TIME), (Double) map.get(CropCircleFileDescription.SPEED), (Double) map.get(CropCircleFileDescription.COURSE), (Double) map.get(CropCircleFileDescription.SENSOR), (Double) map.get(CropCircleFileDescription.VI1), (Double) map.get(CropCircleFileDescription.VI2), (Double) map.get(CropCircleFileDescription.R1), (Double) map.get(CropCircleFileDescription.R2), (Double) map.get(CropCircleFileDescription.R3), (String) map.get(CropCircleFileDescription.APP_RATE));

    }

    private double parseDouble(String doubleValue, String fieldName) throws ParseException {
        try {
            return Double.parseDouble(doubleValue);
        } catch (NumberFormatException e) {
            throw new ParseException("Cropcircle parser: Cannot convert '" + fieldName + "' value to java.lang.Double ", e);
        }
    }

    private int parseInt(String intValue, String fieldName) throws ParseException {
        try {
            return Integer.parseInt(intValue);
        } catch (NumberFormatException e) {
            throw new ParseException("Cropcircle parser: Cannot convert '" + fieldName + "' value to java.lang.Double ", e);
        }
    }

    private Date parseTime(String time, String fieldName) throws ParseException {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HHmmss.SS");
            return sdf.parse(time);
        } catch (java.text.ParseException ex) {
            log.error("Cropcircle parser: Cannot convert '" + fieldName + "' value to java.lang.Date To Create a new Date");
            throw new ParseException(ex);
        }
    }

    @Override
    public boolean ableToParse(Object payload) throws NoSuchAlgorithmException, IOException, AlreadyParsedException{
        String checkSum = MD5Sum.getHex(MD5Sum.createChecksum(new FileInputStream((File)payload)));
        input = new BufferedReader(new FileReader((File)payload));
        String line;

        if(dao.findByNameChecksum(((File)payload).getName(), checkSum) == null){
            if(((File)payload).canRead()){
                while ((line = input.readLine()) != null) {
                    return ableToParse(line);
                }
            }
        } else {
            throw new AlreadyParsedException("File already parsed");
        }
        return false;

    }

    
    /*
     * expects a string with delimiters. It checks length, number of records and if the field is mandatory.
     */
    public boolean ableToParse(String payload) throws AlreadyParsedException, NoSuchAlgorithmException, IOException{
        int counter = 0;
        String field[] = (payload.split(CropCircleFileDescription.getDelimmiter()));
        //check if the number of fields is greater than or equal to the number of mandatory fields and less than maximal number of fields
        if (field.length >= calculateNumberOfMandatoryFields() && field.length <= CropCircleFileDescription.values().length) {
            for (CropCircleFileDescription c : CropCircleFileDescription.values()) {
                if (c.isMandatory()) {
                    //check if field is not empty
                    if (field[counter].trim().equals("")) {
                        return false;
                    }
                    //check if field is correct lenght
                    if (c.getMandatoryLength() != field[counter].trim().length() && (c.getEndPossition() - c.getStartPosition()) + 1 != field[counter].trim().length()) {
                        return false;
                    }
                }
                counter++;
            }
            return payload.split(CropCircleFileDescription.getDelimmiter()).length == calculateNumberOfMandatoryFields();
        }
        return false;
    }

    /*
     * return the number of fields that are mandatory
     */
    public int calculateNumberOfMandatoryFields() {
        int counter = 0;
        for (CropCircleFileDescription c : CropCircleFileDescription.values()) {
            if (c.isMandatory()) {
                counter++;
            }
        }
        return counter;
    }
}
